package dialplan

import (
	"errors"
	"fmt"
	"gitee.com/zackeus/go-boot/freeswitch"
	"gitee.com/zackeus/go-boot/freeswitch/lua"
	"gitee.com/zackeus/go-boot/freeswitch/mod/xmlcurl"
	"github.com/beevik/etree"
)

type (
	Xml struct {
		doc  *etree.Document
		body *etree.Element
	}
)

func New(name string, t Type) (xmlcurl.XmlConf, error) {
	xml := &Xml{
		doc: etree.NewDocument(),
	}
	/* 属性值不转译 */
	xml.doc.WriteSettings.CanonicalAttrVal = true
	xml.doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	document := xml.doc.CreateElement("document")
	document.CreateAttr("type", "freeswitch/xml")

	section := document.CreateElement("section")
	section.CreateAttr("name", "dialplan")

	body := section.CreateElement("context")
	body.CreateAttr("name", name)
	xml.body = body

	secureRtpExtension := CreateExtension("secure_rtp", true)
	secureRtpExtension.AddChild(CreateCondition("${rtp_has_crypto}", "^(AES_CM_128_HMAC_SHA1_32|AES_CM_128_HMAC_SHA1_80)$", BreakNever))

	/* 全局路由表项 */
	globalExtension := CreateExtension("global", true)

	/* 设置主叫被叫 */
	globalConditionDialer := CreateCondition("${dialer_src}", "^$", BreakNever)
	globalConditionDialer.AddChild(CreateAction("set", "dialer_src=${caller_id_number}"))
	globalConditionDialer.AddChild(CreateAction("set", "dialer_dst=${destination_number}"))
	globalConditionDialer.AddChild(CreateAction("export", "nolocal:b_dialer_src=${caller_id_number}"))
	globalConditionDialer.AddChild(CreateAction("export", "nolocal:b_dialer_dst=${destination_number}"))
	globalConditionDialer.AddChild(CreateAction("export", "trunk_number=${destination_number}"))

	/* 设置振铃音 */
	globalConditionRing := etree.NewElement("condition")
	/* 设置回铃音 ringback：183; ring_ready：180; */
	globalConditionRing.AddChild(CreateAction("set", "ringback=$${cn-ring}"))
	/* 设置回铃音 用于回拨情况 */
	globalConditionRing.AddChild(CreateAction("set", "transfer_ringback=$${cn-ring}"))
	/* 设置立即播放回铃音 */
	globalConditionRing.AddChild(CreateAction("set", "instant_ringback=true"))

	/* 设置桥接标识 */
	globalConditionBridged := CreateCondition("${bridged}", "^$", BreakNever)
	globalConditionBridged.AddChild(CreateAction("set", "bridged=false"))
	globalConditionBridged.AddChild(CreateAction("set", "bridge_time="))
	/* 设置 a-leg 收到183或200之后，有媒体通道建立成功 */
	// TODO 目前只有外呼触发
	globalConditionBridged.AddChild(CreateAction("set", fmt.Sprintf("execute_on_pre_originate=lua %s", lua.AlegPreOriginate)))
	/* 设置 b-leg answer后，b-leg执行；即：a,b bridge后 */
	globalConditionBridged.AddChild(CreateAction("export", fmt.Sprintf("nolocal:execute_on_answer=lua %s", lua.BlegAfterAnswer)))

	/* 设置挂断执行脚本 */
	globalConditionHangup := etree.NewElement("condition")
	globalConditionHangup.AddChild(CreateAction("export", "session_in_hangup_hook=true"))
	globalConditionHangup.AddChild(CreateAction("export", fmt.Sprintf("api_hangup_hook=lua %s", lua.HookExtHangup)))

	globalExtension.AddChild(globalConditionDialer)
	globalExtension.AddChild(globalConditionRing)
	globalExtension.AddChild(globalConditionBridged)
	globalExtension.AddChild(globalConditionHangup)

	/* 全局路由CC变量 */
	globalExtensionCC := CreateExtension("global_cc", true)
	/* 初始呼叫设置cc变量 */
	ccConditionBridged := CreateCondition("${bridged}", "^$", BreakOnFalse)
	ccConditionBridged.AddChild(CreateAction("export", "src_agent_no=${presence_agent_no}"))
	ccConditionBridged.AddChild(CreateAction("export", "src_agent_name=${presence_agent_name}"))

	/* cc_side 变量 api 接口设置 */
	globalExtensionCC.AddChild(CreateCondition("${cc_side}", "^(originator)$", BreakOnFalse))
	globalExtensionCC.AddChild(CreateCondition("${b_bridged}", "^$", BreakOnFalse))
	globalExtensionCC.AddChild(ccConditionBridged)

	body.AddChild(secureRtpExtension)
	body.AddChild(globalExtension)
	body.AddChild(globalExtensionCC)

	switch t {
	case Outbound:
		/* 呼出路由 */
		xml.addOutbound()
	case Inbound:
		/* 呼入路由 */
		xml.addInbound()
	default:
	}
	return xml, nil
}

// 添加呼入路由
func (x *Xml) addInbound() {
	globalExtension := CreateExtension("inbound_global", true)

	condition1 := etree.NewElement("condition")
	/* 设置呼叫类型 */
	condition1.AddChild(CreateAction("set", fmt.Sprintf("call_type=%s", freeswitch.CallInbound)))
	condition1.AddChild(CreateAction("export", fmt.Sprintf("nolocal:call_type=%s", freeswitch.CallInbound)))

	/* 设置网关中继 */
	condition2 := CreateCondition("${sip_gateway_name}", "^$", BreakNever)
	condition2.AddChild(CreateAction("set", "sip_gateway_name=${sip_req_user}_${sip_contact_host}_${sip_received_port}", WithInline(true)))

	globalExtension.AddChild(condition1)
	globalExtension.AddChild(condition2)
	x.body.AddChild(globalExtension)
}

// 添加外呼路由
func (x *Xml) addOutbound() {
	globalExtension := CreateExtension("outbound_global", true)

	condition1 := etree.NewElement("condition")
	condition1.AddChild(CreateAction("hash", "insert/${domain_name}-spymap/${caller_id_number}/${uuid}"))
	condition1.AddChild(CreateAction("hash", "insert/${domain_name}-last_dial/${caller_id_number}/${destination_number}"))
	condition1.AddChild(CreateAction("hash", "insert/${domain_name}-last_dial/global/${uuid}"))
	condition1.AddChild(CreateAction("set", "RFC2822_DATE=${strftime(%a, %d %b %Y %T %z)}"))
	/* 设置呼叫类型 */
	condition1.AddChild(CreateAction("set", fmt.Sprintf("call_type=%s", freeswitch.CallOutbound)))
	condition1.AddChild(CreateAction("export", fmt.Sprintf("nolocal:call_type=%s", freeswitch.CallOutbound)))

	/* 设置呼叫超时 */
	condition2 := CreateCondition("${call_timeout}", "^$", BreakNever)
	condition2.AddChild(CreateAction("set", "call_timeout=30"))
	condition2.AddChild(CreateAction("set", "originate_timeout=30"))
	condition2.AddChild(CreateAction("export", "bridge_answer_timeout=30"))
	condition2.AddChild(CreateAction("set", "call_timeout=${call_timeout}", WithAnti(true)))
	condition2.AddChild(CreateAction("set", "originate_timeout=${call_timeout}", WithAnti(true)))
	condition2.AddChild(CreateAction("export", "bridge_answer_timeout=${call_timeout}", WithAnti(true)))

	globalExtension.AddChild(condition1)
	globalExtension.AddChild(condition2)

	/* 内部呼叫 */
	localExtension := CreateExtension("local_extension", false)
	localCondition := CreateCondition("${user_exists(id ${destination_number} ${domain_name})}", "true", BreakOnFalse)
	/* 目的号码处理 */
	localCondition.AddChild(CreateAction("lua", fmt.Sprintf("%s '' ''", lua.HandleDialerDst)))
	/* 本地分机呼叫 */
	localCondition.AddChild(CreateAction("lua", string(lua.LocalCall)))
	localCondition.AddChild(CreateAction("hangup", "${originate_disposition}"))

	localExtension.AddChild(localCondition)

	/* 外部呼叫 */
	externalExtension := CreateExtension("external_extension", true)
	externalCondition := etree.NewElement("condition")
	/* 是否忽略早期媒体 实现bridge真正通话计时 */
	// gatewayCondition.AddChild(CreateAction("export", "nolocal:bridge_early_media=true"))
	externalCondition.AddChild(CreateAction("set", "ignore_early_media=false"))

	externalExtension.AddChild(externalCondition)

	x.body.AddChild(globalExtension)
	x.body.AddChild(localExtension)
	x.body.AddChild(externalExtension)
}

func (x *Xml) BodyElement() *etree.Element {
	return x.body
}

func (x *Xml) MarshalToXml() ([]byte, error) {
	x.doc.Indent(1)
	return x.doc.WriteToBytes()
}

func (x *Xml) UnMarshalFromString(v string) error {
	if x.doc == nil {
		x.doc = etree.NewDocument()
		/* 属性值不转译 */
		x.doc.WriteSettings.CanonicalAttrVal = true
	}
	if err := x.doc.ReadFromString(v); err != nil {
		return err
	}

	body := x.doc.FindElement("./document/section/context")
	if body == nil {
		return errors.New("the element document/section/context is nil")
	}
	x.body = body

	return nil
}

func (x *Xml) WriteToFile(path string) error {
	x.doc.Indent(1)
	return x.doc.WriteToFile(path)
}
